#!/usr/bin/python -S
"""
child_process.py
"""

__author__ = 'Andy Chu'


import os
import signal
import subprocess
import sys

import base
import ioloop
import stream
import log


class ChildRegistry(object):
  """Keeps track of children of this process"""
  def __init__(self):
    # pid -> ChildProcess() instance
    self.children = {}

  def Register(self, pid, child):
    log.trace('Registered %s -> %s', pid, child)
    self.children[pid] = child

  def OnExit(self, pid, status):
    log.trace('Process %d died with status %d', pid, status)
    child = self.children.get(pid)
    if child is None:
      raise RuntimeError("Process died that we didn't start: %s" % pid)

    if os.WIFSIGNALED(status):
      exit_code = None
      signum = os.WTERMSIG(status)
    elif os.WIFEXITED(status):
      exit_code = os.WEXITSTATUS(status)
      signum = None
    else:
      raise AssertionError('Process should die by signal or exit itself')

    # Now call it
    child.OnExit(exit_code, signum)


_child_registry = ChildRegistry()


class ChildProcess(base.EventEmitter):
  """
  Attributes:
    pid: process ID

  emits the "exit" event
  """
  EMITS = ['exit']

  def __init__(self, argv, env=None,
               stdin=None, stdout=None, stderr=None,
               event_loop=None, registry=None):
    base.EventEmitter.__init__(self, event_loop=event_loop)
    # When event emitters created, they generally register themselves as
    # handlers with the event loop.
    registry = registry or _child_registry

    self.argv = argv
    log.trace('Running %s', self.argv)
    try:
      self.p = subprocess.Popen(
          self.argv, env=env, stdout=stdout, stdin=stdin, stderr=stderr)
    except OSError, e:
      # TODO: emit 'error' event?  Why doesn't node.js do this?
      print '!!!!!!!!!!', e
      raise

    self.pid = self.p.pid

    # So the signal handler knows where to send the 'exit' event
    registry.Register(self.pid, self)

    log.trace('ChildProcess %s', self.argv)

    if self.p.stdin:
      log.trace('Creating stdin writeable stream')
      self.stdin = stream.WriteableStream(self.p.stdin)

    if self.p.stdout:
      log.trace('Creating stdout file stream')
      # Public attribute -- now people can register callbacks on me
      self.stdout = stream.ReadableStream(self.p.stdout)

    if self.p.stderr:
      log.trace('Creating stderr file stream')
      # Public attribute -- now people can register callbacks on me
      self.stderr = stream.ReadableStream(self.p.stderr)

  def OnExit(self, exit_code, signum):
    # call functions that registered the 'exit' event
    self.emit('exit', exit_code, signum)

  def __call__(self, fd, events):
    """Called when a SYSTEM event is received to produce USER events."""

    # Only ONE handler should dispatch the byte from the pipe

    # TODO: dispatch on READ -> data, etc.

    # just call Waker.wake(), or add_callback?
    # SigChldHandler should just call add_callback with the PID.
    #
    # This will wake the event loop.  It needs to store the PID somewhere
    # that can be accessed.  I guess they are globals.

  def Kill(self):
    pass

  def _MaybeExit(self):
    """
    what node does is look for the close events
    if you open stdout/stderr, it looks for 2 stream close events, and then the
    process is considered exited?

    hm interesting.  you don't need a sigchld then?
    hm it does have exithandler().  copy this code
    """
    pass

  def __str__(self):
    return '<ChildProcess %s %s>' % (id(self), self.argv[0])


def spawn(argv, cwd=None, env=None):
  pass


_wait_count = 0
def SigchldHandler(signum, frame):
  # signum is always 17, probably not useful

  if 0:
    print '-'*80
    print 'What the heck', signum, frame
    print dir(frame)
    print frame.f_exc_traceback
    print frame.f_back
    print '-'*80

  # BUG:
  # if there is an exception in the child( file doesn't exist), then subprocess calls os.waitpid, reaping the
  # child.  we get the signal here but have nothing to reap.

  global _wait_count
  log.trace('WAITING (count = %d)', _wait_count)
  #print 'WAITING (count = %d)' % _wait_count
  pid, status = os.wait()
  _wait_count += 1

  log.trace('WAITED %s', pid)
  loop = ioloop.IOLoop.instance()
  def f():
    _child_registry.OnExit(pid, status)
  loop.add_callback(f)

  # TODO: write SELF PIPE?


# Hm this is global; doesn't let you pick an IO loop
# TODO: Warn if this isn't called?  The 'exit' event is broken otherwise.
# Also could put in torn.Init()
def RegisterSignalHandlers():
  signal.signal(signal.SIGCHLD, SigchldHandler)
